package com.spzx.common.security.aspect;


import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.uuid.UUID;
import com.spzx.common.redis.util.RedisLockUtilV1;
import com.spzx.common.security.annotation.SpzxCache;
import jakarta.annotation.Resource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Random;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class SpzxCacheAspect {

    @Resource
    RedisTemplate redisTemplate;

    @Resource
    RedissonClient redissonClient;

    //必须和目标方法能够匹配才能生效
    @Around("@annotation(spzxCache)")
    public Object around(ProceedingJoinPoint joinPoint, SpzxCache spzxCache){

        //拼接缓存的键 ：key的拼接规则 使用 注解的缓存key前缀 + 参数列表json字符串
        //通过joinPoint获取方法参数列表
        Object[] args = joinPoint.getArgs();
        //吧参数转换成 json串
        String argsJson = JSON.toJSONString(args);
        //缓存的key
        String cacheKey =  spzxCache.cacheKeyPrefix()+argsJson;
        //锁的key
        String lockKey = spzxCache.lockKeyPrefix()+argsJson;
        String uuid  = UUID.randomUUID().toString().replace("-","");
        //==============================布隆过滤器校验===================================

       //判断是否有布隆过滤器
        if (spzxCache.hasBloomFilter()){
            //通过布隆过滤器判断查询的key是否存在如果不存在直接返回空
            RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(spzxCache.bloomFilterKey());
            long skuId = Long.parseLong(args[0].toString());
            boolean contains = bloomFilter.contains(skuId);
            if (!contains){
                // 数据库一定不存在该数据
                throw  new ServiceException("查询的商品记录不存在："+skuId);
            }

        }

//==============================================================================================




        //查询缓存 如果命中 则直接返回
        //1、 查询缓存
        Object result = redisTemplate.opsForValue().get(cacheKey);
        if (result!=null){
            //命中 直接返回
            return result;
        }

        //
        //2、查询缓存失败
        //2.1 获取分布式锁  使用方法的实参值列表+前缀拼接 操作同一个数据唯一的分布式锁的key
        int timeout = spzxCache.lockKeyTimeout();
        RedisLockUtilV1.lock(lockKey,uuid,timeout);

        //再次查询缓存 作用 ： 假如都是查询 id为1的数据 都在等锁  第一个查询之后 缓存中就有了数据 所以再次查询
        result = redisTemplate.opsForValue().get(cacheKey);
        if (result!=null){
            //命中  返回
            return result;
        }
        // 如果 都没查到 则 执行目标业务方法
        try {
           result = joinPoint.proceed();

        //将目标方法 返回的结果缓存到redis
            //设置缓存有效时间
        int cacheTimeout = spzxCache.cacheTimeout()+new Random().nextInt(spzxCache.cacheRandom());


        redisTemplate.opsForValue().set(cacheKey,result, ObjectUtils.isEmpty(result)?spzxCache.cacheNullTimeout():cacheTimeout, TimeUnit.SECONDS);


        return result;

        } catch (Throwable e) {

            //当redis操作失败 直接调用目标方法查询数据库返回结果   保证了高可用
            try {
                return joinPoint.proceed();
            } catch (Throwable ex) {
                throw new RuntimeException(ex);
            }


        }finally {
            //释放 分布式锁
            RedisLockUtilV1.releaseLock(lockKey,uuid);

        }


    }


}
